#region Header

// <file>
//     <copyright see="prj:///doc/copyright.txt"/>
//     <license see="prj:///doc/license.txt"/>
//     <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
//     <version>$Revision: 2683 $</version>
// </file>

#endregion Header

namespace ICSharpCode.TextEditor.Util
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// A collection that does not allows its elements to be garbage-collected (unless there are other
    /// references to the elements). Elements will disappear from the collection when they are
    /// garbage-collected.
    /// 
    /// The WeakCollection is not thread-safe, not even for read-only access!
    /// No methods may be called on the WeakCollection while it is enumerated, not even a Contains or
    /// creating a second enumerator.
    /// The WeakCollection does not preserve any order among its contents; the ordering may be different each
    /// time the collection is enumerated.
    /// 
    /// Since items may disappear at any time when they are garbage collected, this class
    /// cannot provide a useful implementation for Count and thus cannot implement the ICollection interface.
    /// </summary>
    public class WeakCollection<T> : IEnumerable<T>
        where T : class
    {
        #region Fields

        readonly List<WeakReference> innerList = new List<WeakReference>();

        bool hasEnumerator;

        #endregion Fields

        #region Methods

        /// <summary>
        /// Adds an element to the collection. Runtime: O(n).
        /// </summary>
        public void Add(T item)
        {
            if (item == null)
                throw new ArgumentNullException("item");
            CheckNoEnumerator();
            if (innerList.Count == innerList.Capacity || (innerList.Count % 32) == 31)
                innerList.RemoveAll(delegate(WeakReference r) { return !r.IsAlive; });
            innerList.Add(new WeakReference(item));
        }

        /// <summary>
        /// Removes all elements from the collection. Runtime: O(n).
        /// </summary>
        public void Clear()
        {
            innerList.Clear();
            CheckNoEnumerator();
        }

        /// <summary>
        /// Checks if the collection contains an item. Runtime: O(n).
        /// </summary>
        public bool Contains(T item)
        {
            if (item == null)
                throw new ArgumentNullException("item");
            CheckNoEnumerator();
            foreach (T element in this) {
                if (item.Equals(element))
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Enumerates the collection.
        /// Each MoveNext() call on the enumerator is O(1), thus the enumeration is O(n).
        /// </summary>
        public IEnumerator<T> GetEnumerator()
        {
            if (hasEnumerator)
                throw new InvalidOperationException("The WeakCollection is already being enumerated, it cannot be enumerated twice at the same time. Ensure you dispose the first enumerator before using another enumerator.");
            try {
                hasEnumerator = true;
                for (int i = 0; i < innerList.Count;) {
                    T element = (T)innerList[i].Target;
                    if (element == null) {
                        RemoveAt(i);
                    } else {
                        yield return element;
                        i++;
                    }
                }
            } finally {
                hasEnumerator = false;
            }
        }

        /// <summary>
        /// Removes an element from the collection. Returns true if the item is found and removed,
        /// false when the item is not found.
        /// Runtime: O(n).
        /// </summary>
        public bool Remove(T item)
        {
            if (item == null)
                throw new ArgumentNullException("item");
            CheckNoEnumerator();
            for (int i = 0; i < innerList.Count;) {
                T element = (T)innerList[i].Target;
                if (element == null) {
                    RemoveAt(i);
                } else if (element == item) {
                    RemoveAt(i);
                    return true;
                } else {
                    i++;
                }
            }
            return false;
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        void CheckNoEnumerator()
        {
            if (hasEnumerator)
                throw new InvalidOperationException("The WeakCollection is already being enumerated, it cannot be modified at the same time. Ensure you dispose the first enumerator before modifying the WeakCollection.");
        }

        void RemoveAt(int i)
        {
            int lastIndex = innerList.Count - 1;
            innerList[i] = innerList[lastIndex];
            innerList.RemoveAt(lastIndex);
        }

        #endregion Methods
    }
}